package org.flexthinker.binding.form.support
{
    import org.flexthinker.binding.form.ConfigurableFormModel;
    import org.flexthinker.binding.value.CommitTrigger;
    import org.flexthinker.binding.value.ValueModel;
    import org.flexthinker.binding.value.support.BufferedValueModel;
    import org.flexthinker.binding.value.support.ValueHolder;

    import flash.utils.Dictionary;

    import mx.utils.StringUtil;

    /**
     *
     */
    public class AbstractFormModel implements ConfigurableFormModel
    {
        private var _id:String;
        private var buffered:Boolean = false;
        private var domainObjectHolder:ValueModel;

        private var domainObjectWrapper:Object;

        private var propertyValueModels:Dictionary;

        protected var commitTrigger:CommitTrigger = new CommitTrigger();

        private var fieldNames:Vector.<String> = new Vector.<String>();


        public function AbstractFormModel( domainObject:Object, buffered:Boolean = false )
        {
            domainObjectWrapper = {value:domainObject};
            init( new ValueHolder( domainObjectWrapper, "value" ), buffered )
        }

        private function init( domainObjectHolder:ValueModel, buffered:Boolean = false ):void
        {
            this.domainObjectHolder = domainObjectHolder;
            this.buffered = buffered;
            propertyValueModels = new Dictionary();
        }

        public function getFormObject():Object
        {
            return domainObjectHolder.getValue();
        }


        public function setFormObject( value:Object ):void
        {
            domainObjectHolder.setValue( value );
            //setEnabled(true);
            //revert
        }


        public function getId():String
        {
            return _id;
        }


        public function setId( id:String ):void
        {
            _id = id;
        }

        public function add( propertyName:String ):ValueModel
        {
            return addValueModelFor( propertyName, createValueModel( propertyName ) );
        }


        public function addValueModelFor( propertyName:String, valueModel:ValueModel ):ValueModel
        {
            if ( getFormObject() == null )
            {
                throw new Error("Can't add property as long as form's object is null. Use setFormObject to set a non-null form object");
            }

            if( propertyValueModels[ propertyName ] != null )
            {
                throw new Error( StringUtil.substitute( "Property {0} has been already added", propertyName ) );
            }
            if( ! getFormObject().hasOwnProperty( propertyName ) )
            {
                throw new Error( StringUtil.substitute( "Property {0} doesn't belong to the form Object {1}", propertyName,
                                                        getFormObject() ) );
            }

            if( valueModel is BufferedValueModel )
            {
                BufferedValueModel( valueModel ).setCommitTrigger( commitTrigger );
            }

            valueModel = preProcessNewValueModel( propertyName, valueModel );
            propertyValueModels[ propertyName ] = valueModel;
            fieldNames.push( propertyName );
            postProcessNewValueModel( propertyName, valueModel );
            return valueModel;
        }


        public function getValueModel( formProperty:String ):ValueModel
        {
            var vm:ValueModel = propertyValueModels[formProperty] as ValueModel;
            if( vm == null )
            {
                vm = add( formProperty );
            }
            return vm;
        }


        public function hasValueModel( formProperty:String ):Boolean
        {
            return ( (propertyValueModels[formProperty] as ValueModel ) != null );
        }


        public function getFieldNames():Vector.<String>
        {
            return fieldNames;
        }

        public function revert():void
        {
            // this will cause all buffered value models to revert
            commitTrigger.revert();
        }


        public function commit():void
        {
            // this will cause all buffered value models to commit the buffere values
            commitTrigger.commit();
        }


        public function isBuffered():Boolean
        {
            return buffered;
        }


        public function isCommittable():Boolean
        {
            // this method should be implemented in the super class
            return true;
        }


        public function isDirty():Boolean
        {
            return false; // dirtyValueAndFormModels.length > 0;;
        }

        // --------------------
        // internal methods
        // --------------------
        protected function createValueModel( formProperty:String ):ValueModel
        {
            return buffered ? new BufferedValueModel( createPropertyValueModel( formProperty ) )
                : createPropertyValueModel( formProperty );
        }

        protected function createPropertyValueModel( formProperty:String ):ValueModel
        {
            return new ValueHolder( getFormObject(), formProperty );
        }

        /**
         * Provides a hook for subclasses to optionally decorate a new value model
         * added to this form model.
         */
        protected function preProcessNewValueModel( formProperty:String, valueModel:ValueModel ):ValueModel
        {
            return valueModel;
        }

        /**
         * Provides a hook for subclasses to perform some processing after a new
         * value model has been added to this form model.
         */
        protected function postProcessNewValueModel( formProperty:String, valueModel:ValueModel ):void
        {}

    }
}